use rust_decimal::{Decimal, MathematicalOps};

#[derive(Debug, PartialEq, Clone)]
pub enum Node {
   Add(Box<Node>, Box<Node>),
   Subtract(Box<Node>, Box<Node>),
   Multiply(Box<Node>, Box<Node>),
   Divide(Box<Node>, Box<Node>),
   Power(Box<Node>, Box<Node>),
   Negative(Box<Node>),
   Number(Decimal),
}

impl Node {
   pub fn eval(&self) -> Decimal {
      use Node::*;

      match self {
         Add(left_expr, right_expr) => left_expr.eval() + right_expr.eval(),
         Subtract(left_expr, right_expr) => left_expr.eval() - right_expr.eval(),
         Multiply(left_expr, right_expr) => left_expr.eval() * right_expr.eval(),
         Divide(left_expr, right_expr) => left_expr.eval() / right_expr.eval(),
         Power(left_expr, right_expr) => {
            left_expr.eval().powd(right_expr.eval())
         }
         Negative(expr) => -expr.eval(),
         Number(num) => *num,
      }
   }
}

#[cfg(test)]
mod tests {
   use rust_decimal::dec;

   use super::*;

   #[test]
   fn test_eval() {
      assert_eq!(Node::Number(dec!(1)).eval(), dec!(1));
      assert_eq!(
         Node::Add(
            Box::new(Node::Number(dec!(1))),
            Box::new(Node::Number(dec!(2))),
         )
         .eval(),
         dec!(3)
      );
      assert_eq!(
         Node::Multiply(
            Box::new(Node::Number(dec!(2))),
            Box::new(Node::Number(dec!(2))),
         )
         .eval(),
         dec!(4)
      );
      assert_eq!(
         Node::Add(
            Box::new(Node::Number(dec!(1))),
            Box::new(Node::Number(
               Node::Multiply(
                  Box::new(Node::Number(dec!(2))),
                  Box::new(Node::Number(dec!(2))),
               )
               .eval()
            ))
         )
         .eval(),
         dec!(5)
      );
   }
}
